#include <iostream>
#include <string>
#include <vector>
#include <fstream>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include "bundle.h"
#include <experimental/filesystem>
#include <jsoncpp/json/json.h>

namespace cloud
{
    namespace fs = std::experimental::filesystem;
    
    class FileUtil{
        private:
            std::string _filename;
        public:
            FileUtil(const std::string filename):_filename(filename){}
            int64_t FileSize()
            {
                struct stat st;
                if(stat(_filename.c_str(), &st) < 0)
                {
                    std::cout << "get file size failed." << std::endl;
                    return -1;
                }
                return st.st_size;
            }
            time_t LastATime()
            {
                struct stat st;
                if(stat(_filename.c_str(), &st) < 0)
                {
                    std::cout << "get file LastATime failed." << std::endl;
                    return -1;
                }
                return st.st_atime;
            }
            time_t LastMTime()
            {
                struct stat st;
                if(stat(_filename.c_str(), &st) < 0)
                {
                    std::cout << "get file LastMTime failed." << std::endl;
                    return -1;
                }
                return st.st_mtime;
            }
            std::string FileName()
            {
                //a/b.txt
                size_t pos = _filename.find_last_of("/");
                if(pos == std::string::npos) return _filename;
                return _filename.substr(pos + 1);
            }
            bool GetPosLen(std::string *body, size_t pos, size_t len)
            {
                int64_t fsize = this->FileSize();
                if(pos + len > fsize)
                {
                    std::cout << "len error\n";
                    return false;
                }
                std::ifstream ifs;
                ifs.open(_filename, std::ios::binary);
                if(ifs.is_open() == false) 
                {
                    std::cout << "read : open failed\n";
                    return false;
                }
                
                ifs.seekg(pos, std::ios::beg);
                body->resize(len);
                ifs.read(&(*body)[0], len);
                if(ifs.good() == false)
                {
                    std::cout << "read file content failed\n";
                    ifs.close();
                    return false;
                }
                ifs.close();
                return true;
            }
            bool GetContent(std::string *body)
            {
                int64_t fsize = this->FileSize();
                return GetPosLen(body, 0, fsize);
            }
            bool SetContent(std::string *body)
            {
                std::ofstream ofs;
                ofs.open(_filename, std::ios::binary);
                if(ofs.is_open() == false) 
                {
                    std::cout << "write : open failed\n";
                    return false;
                }
                ofs.write(&(*body)[0], body->size());
                if(ofs.good() == false)
                {
                    std::cout << "write file content failed\n";
                    ofs.close();
                    return false;
                }
                ofs.close();
                return true;
            }
            bool Remove()
            {
                if(this->Exists() == false) return false;
                remove(_filename.c_str());
                return true;
            }
            bool Compress(const std::string &packname)
            {
                std::string body;
                if(GetContent(&body) == false) {
                    std::cout << "compress get file failed.\n";
                    return false;
                }
                std::string packed = bundle::pack(bundle::LZIP, body);
                FileUtil fu(packname);
                if(fu.SetContent(&packed) == false) {
                    std::cout << "compress write file failed.\n";
                    return false;
                }
                return true;
            }
            bool UnCompress(const std::string &filename)
            {
                std::string body;
                if(GetContent(&body) == false) {
                    std::cout << "uncompress get file failed.\n";
                    return false;
                }
                std::string unpacked = bundle::unpack(body);
                FileUtil fu(filename);
                if(fu.SetContent(&unpacked) == false) {
                    std::cout << "uncompress write file failed.\n";
                    return false;
                }
                return true;
            }
            bool Exists()
            {
                return fs::exists(_filename);
            }
            bool CreateDirectory()
            {
                if(this->Exists()) return true;
                return fs::create_directories(_filename);
            }
            bool ScanDirectory(std::vector<std::string> *arry)
            {
                for (auto &p : fs::directory_iterator(_filename))
                {
                    if(fs::is_directory(p)) continue;
                    arry->push_back(fs::path(p).relative_path().string());
                }
                return true;

            }
    };
    class JsonUtil{
        public:
            static bool Serialize(const Json::Value &root, std::string *str)
            {
                Json::StreamWriterBuilder swb;
                std::unique_ptr<Json::StreamWriter> sw(swb.newStreamWriter());
                std::stringstream ss;
                sw->write(root, &ss);
                *str = ss.str();
                return true;
            }
            static bool UnSerialize(const std::string &str, Json::Value *root)
            {
                Json::CharReaderBuilder crb;
                std::unique_ptr<Json::CharReader> cr(crb.newCharReader());
                std::string err;
                bool ret = cr->parse(str.c_str(), str.c_str() + str.size(), root, &err);
                if(ret == false)
                {
                    std::cout << "parce err:" << err << std::endl;
                    return false;
                }
                return true;
            }
    };

}
